from core import *
from lib import hexdump


class MappingAdapterError(AdapterError):
    pass


#===============================================================================
# adapters
#===============================================================================
class MappingAdapter(Adapter):
    """
    Defines separate mappings for decoding and encoding of values; you can also
    supply default values, in case the mapping does not exist (otherwise it's
    an error).
    The mapping need not be symmetrical. Use the Enum construct for symmetrical,
    one-to-one, name-to-value mapping. See also Enum.
    
    Constructor:
    * subcon - the subconstruct to adapt
    * encoding - the encoding dictionary of {decoded : encoded} items
    * decoding - the encoding dictionary of {encoded : decoded} items
    * encoding_default - the default value for encoding; if no mapping is 
      defined for the object, and this default value is not set, an exception
      occurs
    * decoding_default - the default value for decoding; if no mapping is 
      defined for the object, and this default value is not set, an exception
      occurs
    """
    __slots__ = ["encoding", "decoding", "encoding_default", "decoding_default"]
    class NoDefault(object):
        pass
    def __init__(self, subcon, encoding, decoding, encoding_default = NoDefault, 
    decoding_default = NoDefault):
        Adapter.__init__(self, subcon)
        self.encoding = encoding
        self.decoding = decoding
        self.encoding_default = encoding_default
        self.decoding_default = decoding_default
    def _encode(self, obj):
        encobj = self.encoding.get(obj, self.encoding_default)
        if encobj is self.NoDefault:
            raise MappingAdapterError("undefined mapping for %r" % (obj,))
        return encobj
    def _decode(self, obj):
        decobj = self.decoding.get(obj, self.decoding_default)
        if decobj is self.NoDefault:
            raise MappingAdapterError("undefined mapping for %r" % (obj,))
        return decobj

class StringAdapter(Adapter):
    """
    Adapts a 'raw string' (a sequence of bytes) into a string object, based on
    the given encoding (None means no encoding to use).
    This class is not intended for direct use. See String instead.
    """
    __slots__ = ["encoding"]
    def __init__(self, subcon, encoding = None):
        Adapter.__init__(self, subcon)
        self.encoding = encoding
    def _encode(self, obj):
        if self.encoding is not None:
            obj = obj.encode(self.encoding)
        return obj
    def _decode(self, obj):
        obj = "".join(obj)
        if self.encoding is not None:
            obj = obj.decode(self.encoding)
        return obj

class CStringAdapter(StringAdapter):
    """
    An adapter for the CString construct (removes/adds the terminator)
    This class is not intended for direct use. See CString instead.
    """
    __slots__ = ["terminator"]
    def __init__(self, subcon, encoding = None, terminator = "\x00"):
        StringAdapter.__init__(self, subcon, encoding)
        self.terminator = terminator
    def _encode(self, obj):
        return StringAdapter._encode(self, obj) + self.terminator
    def _decode(self, obj):
        return StringAdapter._decode(self, obj)[:-len(self.terminator)]

class LengthValueAdapter(Adapter):
    """
    An adapter for length-value constructs. This class is not intended for 
    direct use. See LengthValue instead.
    """
    __slots__ = ["lengthname", "valuename"]
    def __init__(self, subcon, lengthname = "length", valuename = "value"):
        Adapter.__init__(self, subcon)
        self.lengthname = lengthname
        self.valuename = valuename
    def _decode(self, obj):
        return getattr(obj, self.valuename)
    def _encode(self, obj):
        encobj = Container()
        setattr(encobj, self.lengthname, len(obj))
        setattr(encobj, self.valuename, obj)
        return encobj

class PaddedStringAdapter(Adapter):
    """
    Takes care of padding/unpadding strings for fixed-length string fields.
    This class is not intended for direct use. See String instead.
    """
    __slots__ = ["length", "paddir", "trimdir", "padchar"]
    LEFT = "left"
    RIGHT = "right"
    BOTH = "both"
    
    def __init__(self, subcon, length, paddir = RIGHT, trimdir = RIGHT, 
    padchar = "\x00"):
        assert paddir in (self.LEFT, self.RIGHT, self.BOTH)
        assert trimdir in (self.LEFT, self.RIGHT)
        Adapter.__init__(self, subcon)
        self.length = length
        self.paddir = paddir
        self.trimdir = trimdir
        self.padchar = padchar
    def _decode(self, obj):
        if self.paddir == self.LEFT:
            return obj.lstrip(self.padchar)
        elif self.paddir == self.RIGHT:
            return obj.rstrip(self.padchar)
        else:
            return obj.strip(self.padchar)
    def _encode(self, obj):
        if self.trimdir == self.LEFT:
            obj = obj[-self.length:]
        else:
            obj = obj[:self.length]
        if self.paddir == self.LEFT:
            return obj.rjust(self.length, self.padchar)
        elif self.paddir == self.RIGHT:
            return obj.ljust(self.length, self.padchar)
        else:
            return obj.center(self.length, self.padchar)

class FlagsAdapter(Adapter):
    """
    Decodes a number into a set of "flags"; encodes a set of flags into a 
    number
    
    Constructor:
    * subcon - the subconstruct to adapt
    * flags - a dictionary of (flagname, bitmask) pairs
    
    Example:
    FlagsAdapter(
        UBInt8("blah"), 
        {
            "a" : 0x01,
            "b" : 0x02,
            "c" : 0x04,
            "d" : 0x08,
            "e" : 0x10,
            "f" : 0x20,
            "g" : 0x40,
            "h" : 0x80,
        }
    )
    """
    __slots__ = ["flags"]
    def __init__(self, subcon, flags):
        Adapter.__init__(self, subcon)
        self.flags = flags
    def _encode(self, obj):
        result = 0
        for name, value in self.flags.iteritems():
            if getattr(obj, name):
                result |= value
        return result
    def _decode(self, obj):
        newobj = Container()
        for name, value in self.flags.iteritems():
            setattr(newobj, name, bool(obj & value))
        return newobj

class ExprAdapter(Adapter):
    """
    Adapts the given value based on a decoding/encoding tranformation. The
    tranformation is a function that takes the original value and returns the
    corresponding transformed value. The decoding/encoding transformators are
    expected to be "mirror images" of each other.
    It is useful for simple tasks (like multiplying the value by a constant) 
    instead of having to subclass an Adapter class.
    
    * subcon - the subconstruct to adapt
    * decoder - the decoder function, takes the encoded object as an argument
    * encoder - the encoder function, takes the decoded object as an argument
    
    Example:
    ExprAdapter(
        UBInt8("blah"),
        lambda obj : obj * 9,
        lambda obj : obj / 9
    )
    """
    __slots__ = ["_encode", "_decode"]
    def __init__(self, subcon, decoder, encoder):
        Adapter.__init__(self, subcon)
        self._decode = decoder
        self._encode = encoder

class HexDumpAdapter(Adapter):
    __slots__ = ["linesize"]
    
    class HexPrinter(object):
        __slots__ = ["data", "linesize"]
        def __init__(self, data, linesize = 16):
            self.data = data
            self.linesize = linesize
        def __repr__(self):
            return repr(self.data)
        def __str__(self):
            return self.__pretty_str__()
        def __pretty_str__(self, nesting = 1):
            sep = "\n" + "    " * nesting
            return sep + sep.join(hexdump(self.data))
    
    def __init__(self, subcon, linesize = 16):
        Adapter.__init__(self, subcon)
        self.linesize = linesize
    def _decode(self, obj):
        return self.HexPrinter(obj, self.linesize)
    def _encode(self, obj):
        return obj.data



















